package com.jiangwei.flv.factories;

import com.alibaba.fastjson.util.IOUtils;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;

import javax.servlet.AsyncContext;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * javacv转码<br/>
 * 流来源不是视频H264格式,音频AAC格式 转码为视频H264格式,音频AAC格式
 * 
 * @author gc.x
 */
@Slf4j
public class ConverterTranFactories implements Converter, Runnable {
	public volatile boolean running = true;
	/**
	 * 读流器
	 */
	private final FFmpegFrameGrabber grabber;
	/**
	 * 转码器
	 */
	private FFmpegFrameRecorder recorder;
	/**
	 * 转FLV格式的头信息<br/>
	 * 如果有第二个客户端播放首先要返回头信息
	 */
	private byte[] headers;
	/**
	 * 保存转换好的流
	 */
	private ByteArrayOutputStream stream;
	/**
	 * 流地址，h264,aac
	 */
	private final String url;
	/**
	 * 流输出
	 */
	private final List<AsyncContext> outEntities;

	/**
	 * key用于表示这个转换器
	 */
	private final String key;

	/**
	 * 转换队列
	 */
	private final Map<String, Converter> factories;

	public ConverterTranFactories(String url, String key, Map<String, Converter> factories,
								  List<AsyncContext> outEntities, FFmpegFrameGrabber grabber) {
		this.url = url;
		this.key = key;
		this.factories = factories;
		this.outEntities = outEntities;
		this.grabber = grabber;
	}

	@Override
	public void run() {
		try {
			log.info("this url:{} converterTranFactories start", url);
			grabber.setFrameRate(25);
			if (grabber.getImageWidth() > 1920) {
				grabber.setImageWidth(1920);
			}
			if (grabber.getImageHeight() > 1080) {
				grabber.setImageHeight(1080);
			}
			stream = new ByteArrayOutputStream();
			recorder = new FFmpegFrameRecorder(stream, grabber.getImageWidth(), grabber.getImageHeight(),
					grabber.getAudioChannels());
			recorder.setInterleaved(true);
			recorder.setVideoOption("preset", "ultrafast");
			recorder.setVideoOption("tune", "zerolatency");
			recorder.setVideoOption("crf", "25");
			recorder.setGopSize(50);
			recorder.setFrameRate(25);
			recorder.setSampleRate(grabber.getSampleRate());
			if (grabber.getAudioChannels() > 0) {
				recorder.setAudioChannels(grabber.getAudioChannels());
				recorder.setAudioBitrate(grabber.getAudioBitrate());
				recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
			}
			recorder.setFormat("flv");
			recorder.setVideoBitrate(grabber.getVideoBitrate());
			recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
			recorder.start();
			if (headers == null) {
				headers = stream.toByteArray();
				stream.reset();
				writeResponse(headers);
			}
			int nullNumber = 0;
			while (running) {
				// 抓取一帧
				Frame f = grabber.grab();
				if (f != null) {
					try {
						// 转码
						recorder.record(f);
					} catch (Exception e) {
						log.error(e.getMessage());
					}
					if (stream.size() > 0) {
						byte[] b = stream.toByteArray();
						stream.reset();
						writeResponse(b);
						if (outEntities.isEmpty()) {
							log.info("没有输出退出");
							break;
						}
					}
				} else {
					nullNumber++;
					if (nullNumber > 200) {
						break;
					}
				}
				Thread.sleep(5);
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		} finally {
			closeConverter();
			completeResponse();
			log.info("this url:{} converterTranFactories exit", url);
			factories.remove(this.key);
		}
	}

	/**
	 * 输出FLV视频流
	 */
	public void writeResponse(byte[] b) {
		Iterator<AsyncContext> it = outEntities.iterator();
		while (it.hasNext()) {
			AsyncContext o = it.next();
			try {
				o.getResponse().getOutputStream().write(b);
			} catch (Exception e) {
				e.printStackTrace();
				// log.info("移除一个输出");
				it.remove();
			}
		}
	}

	/**
	 * 退出转换
	 */
	public void closeConverter() {
		IOUtils.close(grabber);
		IOUtils.close(recorder);
		IOUtils.close(stream);
	}

	/**
	 * 关闭异步响应
	 */
	public void completeResponse() {
        for (AsyncContext o : outEntities) {
            o.complete();
        }
	}

	@Override
	public String getKey() {
		return this.key;
	}

	@Override
	public String getUrl() {
		return this.url;
	}

	@Override
	public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException {
		if (headers == null) {
			outEntities.add(entity);
		} else {
			entity.getResponse().getOutputStream().write(headers);
			entity.getResponse().getOutputStream().flush();
			outEntities.add(entity);
		}
	}

	@Override
	public void exit() {
		this.running = false;
	}

}
